package com.gitee.feizns.web.aspectj.decrypt;

import com.gitee.feizns.bean.PropertyUtils;
import com.gitee.feizns.reflect.TypeUtils;
import com.gitee.feizns.web.ex.BadRequestException;
import com.gitee.feizns.web.utils.ParamsUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @author feizns
 * @since 2019/7/16
 */
@Aspect
public class DecryptInterceptor implements Ordered {

    @Pointcut("@annotation(com.gitee.feizns.web.aspectj.decrypt.Decrypt)")
    public void decrypt() {}

    private KeyUtils keyUtils;

    private int order;

    public DecryptInterceptor(KeyUtils keyUtils) {
        this(keyUtils, Integer.MIN_VALUE);
    }

    public DecryptInterceptor(KeyUtils keyUtils, int order) {
        this.order = order;
        this.keyUtils = keyUtils;
    }

    @Around("decrypt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();

        Signature signature = joinPoint.getSignature();
        if ( signature instanceof MethodSignature ) {
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            String[] names = ParamsUtils.getNames(method);
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                args[i] = validate(args[i], parameters[i], names[i]);

                if ( ignoreType(parameters[i].getType()) == false )
                    this.handle(args[i]);
            }
        }

        return joinPoint.proceed(args);
    }

    private void handle(Object entity) {
        if ( entity != null ) {
            PropertyUtils.rwProps(entity).forEach(prop -> {

                Field field = prop.getField();
                if ( field != null ) {
                    prop.set(validate(prop.val(), field, field.getName()));

                    if ( ignoreType(field.getType()) == false )
                        handle(prop.val());
                }
            });
        }
    }

    private boolean ignoreType(Class<?> type) {
        return TypeUtils.isPrimitive(type) || type == Class.class;
    }

    private Object validate(Object original, AnnotatedElement element, String name) {
        Decrypt decrypt = element.getAnnotation(Decrypt.class);
        if ( decrypt != null ) {
            if ( original == null && decrypt.required() )
                throw new BadRequestException(String.format("Parameter %s cannot be empty.", name));
            else if ( original != null && original.getClass() == String.class ) {
                String newVal = keyUtils.decrypt(original.toString());
                if ( newVal.length() < decrypt.min() || newVal.length() > decrypt.max() )
                    throw new BadRequestException(String.format("Parameter %s length must be between %s and %s.", name, decrypt.min(), decrypt.max()));
                return newVal;
            }
        }
        return original;
    }

    @Override
    public int getOrder() {
        return order;
    }

}
